package utils

import (
	"fmt"
	"github.com/jinzhu/gorm"
	"reflect"
	"sort"
	"strings"
	"time"
)

//ScopeCreateSQLv1916 使用gorm scope生成create sql和vars（适用于gorm v1.9.16版本）
// copy from gorm source code
// scope.SQL, scope.SQLVars
func ScopeCreateSQLv1916(scope *gorm.Scope) {
	var (
		columns, placeholders        []string
		blankColumnsWithDefaultValue []string
	)

	changeableField := func(scope *gorm.Scope, field *gorm.Field) bool {
		if selectAttrs := scope.SelectAttrs(); len(selectAttrs) > 0 {
			for _, attr := range selectAttrs {
				if field.Name == attr || field.DBName == attr {
					return true
				}
			}
			return false
		}

		for _, attr := range scope.OmitAttrs() {
			if field.Name == attr || field.DBName == attr {
				return false
			}
		}
		return true
	}

	addExtraSpaceIfExist := func(str string) string {
		if str != "" {
			return " " + str
		}
		return ""
	}

	for _, field := range scope.Fields() {
		if changeableField(scope, field) {
			if field.IsNormal && !field.IsIgnored {
				if field.IsBlank && field.HasDefaultValue {
					blankColumnsWithDefaultValue = append(blankColumnsWithDefaultValue, scope.Quote(field.DBName))
					scope.InstanceSet("gorm:blank_columns_with_default_value", blankColumnsWithDefaultValue)
				} else if !field.IsPrimaryKey || !field.IsBlank {
					columns = append(columns, scope.Quote(field.DBName))
					placeholders = append(placeholders, scope.AddToVars(field.Field.Interface()))
				}
			} else if field.Relationship != nil && field.Relationship.Kind == "belongs_to" {
				for _, foreignKey := range field.Relationship.ForeignDBNames {
					if foreignField, ok := scope.FieldByName(foreignKey); ok && !changeableField(scope, foreignField) {
						columns = append(columns, scope.Quote(foreignField.DBName))
						placeholders = append(placeholders, scope.AddToVars(foreignField.Field.Interface()))
					}
				}
			}
		}
	}

	var (
		returningColumn = "*"
		quotedTableName = scope.QuotedTableName()
		primaryField    = scope.PrimaryField()
		extraOption     string
		insertModifier  string
	)

	if str, ok := scope.Get("gorm:insert_option"); ok {
		extraOption = fmt.Sprint(str)
	}
	if str, ok := scope.Get("gorm:insert_modifier"); ok {
		insertModifier = strings.ToUpper(fmt.Sprint(str))
		if insertModifier == "INTO" {
			insertModifier = ""
		}
	}

	if primaryField != nil {
		returningColumn = scope.Quote(primaryField.DBName)
	}

	lastInsertIDOutputInterstitial := scope.Dialect().LastInsertIDOutputInterstitial(quotedTableName, returningColumn, columns)
	var lastInsertIDReturningSuffix string
	if lastInsertIDOutputInterstitial == "" {
		lastInsertIDReturningSuffix = scope.Dialect().LastInsertIDReturningSuffix(quotedTableName, returningColumn)
	}

	//特殊处理，支持不绑定参数的sql
	if scopeNoBindParamSql(scope) {
		vars := []string{}
		for _, va := range scope.SQLVars {
			vars = append(vars, interfaceToSqlString(va))
		}
		scope.Raw(fmt.Sprintf(
			"INSERT%v INTO %v (%v)%v VALUES (%v)%v%v",
			addExtraSpaceIfExist(insertModifier),
			scope.QuotedTableName(),
			strings.Join(columns, ","),
			addExtraSpaceIfExist(lastInsertIDOutputInterstitial),
			strings.Join(vars, ","),
			addExtraSpaceIfExist(extraOption),
			addExtraSpaceIfExist(lastInsertIDReturningSuffix),
		))
		return
	}

	scope.Raw(fmt.Sprintf(
		"INSERT%v INTO %v (%v)%v VALUES (%v)%v%v",
		addExtraSpaceIfExist(insertModifier),
		scope.QuotedTableName(),
		strings.Join(columns, ","),
		addExtraSpaceIfExist(lastInsertIDOutputInterstitial),
		strings.Join(placeholders, ","),
		addExtraSpaceIfExist(extraOption),
		addExtraSpaceIfExist(lastInsertIDReturningSuffix),
	))
}

//ScopeUpdateSQLv1916 使用gorm scope生成update sql和vars（适用于gorm v1.9.16版本）
// copy from gorm source code
// scope.SQL, scope.SQLVars
func ScopeUpdateSQLv1916(scope *gorm.Scope) {
	var addExtraSpaceIfExist = func(str string) string {
		if str != "" {
			return " " + str
		}
		return ""
	}
	var changeableField = func(scope *gorm.Scope, field *gorm.Field) bool {
		if selectAttrs := scope.SelectAttrs(); len(selectAttrs) > 0 {
			for _, attr := range selectAttrs {
				if field.Name == attr || field.DBName == attr {
					return true
				}
			}
			return false
		}

		for _, attr := range scope.OmitAttrs() {
			if field.Name == attr || field.DBName == attr {
				return false
			}
		}

		return true
	}
	if scopeNoBindParamSql(scope) {
		scope.InstanceSet("skip_bindvar", true)
	}

	if !scope.HasError() {
		var sqls []string

		if updateAttrs, ok := scope.InstanceGet("gorm:update_attrs"); ok {
			// Sort the column names so that the generated SQL is the same every time.
			updateMap := updateAttrs.(map[string]interface{})
			var columns []string
			for c := range updateMap {
				columns = append(columns, c)
			}
			sort.Strings(columns)

			for _, column := range columns {
				value := updateMap[column]
				sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(column), scope.AddToVars(value)))
			}
		} else {
			for _, field := range scope.Fields() {
				if changeableField(scope, field) {
					if !field.IsPrimaryKey && field.IsNormal && (field.Name != "CreatedAt" || !field.IsBlank) {
						if !field.IsForeignKey || !field.IsBlank || !field.HasDefaultValue {
							sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(field.DBName), scope.AddToVars(field.Field.Interface())))
						}
					} else if relationship := field.Relationship; relationship != nil && relationship.Kind == "belongs_to" {
						for _, foreignKey := range relationship.ForeignDBNames {
							if foreignField, ok := scope.FieldByName(foreignKey); ok && !changeableField(scope, foreignField) {
								sqls = append(sqls,
									fmt.Sprintf("%v = %v", scope.Quote(foreignField.DBName), scope.AddToVars(foreignField.Field.Interface())))
							}
						}
					}
				}
			}
		}

		var extraOption string
		if str, ok := scope.Get("gorm:update_option"); ok {
			extraOption = fmt.Sprint(str)
		}

		if len(sqls) == 0 {
			return
		}

		//特殊处理，支持不绑定参数的sql
		if scopeNoBindParamSql(scope) {
			sql := fmt.Sprintf(
				"UPDATE %v SET %v%v%v",
				scope.QuotedTableName(),
				strings.Join(sqls, ", "),
				addExtraSpaceIfExist(scope.CombinedConditionSql()),
				addExtraSpaceIfExist(extraOption),
			)
			for _, va := range scope.SQLVars {
				//loggers.Debug.Printf("%d", i)
				//loggers.Debug.Printf("%s", va)
				sql = strings.Replace(sql, "?", interfaceToSqlString(va), 1)
			}
			scope.Raw(sql).Exec()
			return
		}

		if len(sqls) > 0 {
			scope.Raw(fmt.Sprintf(
				"UPDATE %v SET %v%v%v",
				scope.QuotedTableName(),
				strings.Join(sqls, ", "),
				addExtraSpaceIfExist(scope.CombinedConditionSql()),
				addExtraSpaceIfExist(extraOption),
			)).Exec()
		}
	}
}

func scopeNoBindParamSql(scope *gorm.Scope) bool {
	v, ok := scope.InstanceGet("nobind_sql")
	return ok && v.(bool) == true
}

func InterfaceToSqlString(a interface{}) string {
	return interfaceToSqlString(a)
}

//转为sql用字符串
func interfaceToSqlString(a interface{}) string {
	if a == nil {
		return "null"
	}
	t := reflect.ValueOf(a)

	//针对特殊的一些类型
	switch a.(type) {
	case time.Time:
		a = a.(time.Time).Format(time.RFC3339Nano)
		t = reflect.ValueOf(a)
	}

	switch t.Kind() {
	case reflect.Int:
		fallthrough
	case reflect.Int8:
		fallthrough
	case reflect.Int16:
		fallthrough
	case reflect.Int32:
		fallthrough
	case reflect.Int64:
		fallthrough
	case reflect.Uint:
		fallthrough
	case reflect.Uint8:
		fallthrough
	case reflect.Uint16:
		fallthrough
	case reflect.Uint32:
		fallthrough
	case reflect.Uint64:
		return fmt.Sprintf("%d", a)
	case reflect.Float32:
		fallthrough
	case reflect.Float64:
		return fmt.Sprintf("%f", a)
	case reflect.Ptr:
		if t.IsNil() {
			return "null"
		}
		//指针类型的取值后在处理
		return interfaceToSqlString(t.Elem().Interface())
	}
	return fmt.Sprintf("'%s'", a)
}
